#pragma once

#include <string>
#include <vector>
#include <unordered_map>
#include <stack>

using RegType = int;
using Reg = std::unordered_map<std::string_view, RegType>;
enum CMP : int
{
    EQUAL,
    LESS,
    GREAT,
};

std::vector<std::string_view> toLines( std::string_view str ) {
    std::vector<std::string_view> lines;
    size_t begin = 0;
    size_t end = 0;
    std::string_view tmp;
    while ( end != std::string_view::npos )
    {
        end = str.find( '\n', begin );
        tmp = str.substr( begin, end - begin );
        if ( tmp.size() > 0 && tmp.find_first_not_of( ' ' ) < tmp.find( ';' ) )
        {
            lines.push_back( tmp.substr( 0, tmp.find( ';' ) ) );
        }
        begin = end + 1;
    }
    return lines;
}

std::vector<std::string_view> toArgs( std::string_view line ) {
    std::vector<std::string_view> args;
    size_t pos = line.find_first_not_of( ' ' );
    size_t index = pos;
    while ( pos < line.size() )
    {
        if ( line[pos] == '\'' )
        {
            index = line.find( '\'', pos + 1 );
            index = line.find_first_of( ", ", index );
        } else
        { index = line.find_first_of( ", ", pos ); }
        args.push_back( line.substr( pos, index - pos ) );
        pos = line.find_first_not_of( ", ", index );
    }
    return args;
}

RegType getValue( const Reg & regs, std::string_view data ) {
    if ( data.find_first_of( "abcdefghijklmnopqrstuvwxyz" ) != std::string_view::npos )
    {
        return regs.at( data );
    }
    return std::stoi( static_cast<std::string>( data ) );
}

CMP compare( RegType first, RegType second ) {
    if ( first < second )
    {
        return LESS;
    } else if ( first > second )
    {
        return GREAT;
    } else
    { return EQUAL; }
}

std::string assembler_interpreter( std::string program ) {
    Reg regs;
    std::stack<size_t> stack;
    CMP cmp;
    std::vector<std::string_view> lines = toLines( program );
    std::vector<std::string_view> args;
    std::string output;
    // labels
    std::unordered_map<std::string_view, size_t> labels;
    for ( size_t i = 0; i < lines.size(); ++i )
    {
        args = toArgs( lines[i] );
        if ( args[0].back() == ':' )
        {
            labels[args[0].substr( 0, args[0].size() - 1 )] = i;
        }
    }
    // main
    for ( size_t i = 0; i < lines.size(); ++i )
    {
        args = toArgs( lines[i] );
        if ( args[0] == "mov" )
        {
            regs[args[1]] = getValue( regs, args[2] );
        } else if ( args[0] == "inc" )
        {
            regs[args[1]]++;
        } else if ( args[0] == "dec" )
        {
            regs[args[1]]--;
        } else if ( args[0] == "add" )
        {
            regs[args[1]] += getValue( regs, args[2] );
        } else if ( args[0] == "sub" )
        {
            regs[args[1]] -= getValue( regs, args[2] );
        } else if ( args[0] == "mul" )
        {
            regs[args[1]] *= getValue( regs, args[2] );
        } else if ( args[0] == "div" )
        {
            regs[args[1]] /= getValue( regs, args[2] );
        } else if ( args[0] == "cmp" )
        {
            cmp = compare( getValue( regs, args[1] ), getValue( regs, args[2] ) );
        } else if ( args[0] == "jmp" || ( args[0] == "jne" && cmp != EQUAL ) || ( args[0] == "je" && cmp == EQUAL )
                    || ( args[0] == "jge" && ( cmp == EQUAL || cmp == GREAT ) ) || ( args[0] == "jg" && cmp == GREAT ) || ( args[0] == "jl" && cmp == LESS )
                    || ( args[0] == "jle" && ( cmp == EQUAL || cmp == LESS ) ) )
        {
            i = labels[args[1]];
        } else if ( args[0] == "call" )
        {
            stack.push( i );
            i = labels[args[1]];
        } else if ( args[0] == "ret" )
        {
            i = stack.top();
            stack.pop();
        } else if ( args[0] == "msg" )
        {
            for ( size_t j = 1; j < args.size(); ++j )
            {
                if ( args[j][0] == '\'' )
                {
                    output += args[j].substr( 1, args[j].size() - 2 );
                } else
                    output += std::to_string( getValue( regs, args[j] ) );
            }
        } else if ( args[0] == "end" )
        { return output; }
    }
    return "-1";
}
